2-2 进阶控制器守卫、自定义装饰器、全局守卫两种创建方式
本节扩展守卫的高级用法,包括控制器级别守卫、自定义 @Public() 装饰器实现路由豁免,以及全局守卫的两种创建方式和执行顺序。
守卫的三个应用层级
| 层级 | 装饰器位置 | 影响范围 |
|---|---|---|
| 路由级别 | 方法上方 @UseGuards() | 仅当前路由 |
| 控制器级别 | 类上方 @UseGuards() | 控制器内所有路由 |
| 全局级别 | app.useGlobalGuards() 或 APP_GUARD | 所有控制器所有路由 |
// 控制器级别 — 所有路由都需要鉴权
@Controller('user')
@UseGuards(AuthGuard('jwt'))
export class UserController {
@Get('profile') // 需要鉴权
getProfile() {}
@Get('list') // 需要鉴权
findAll() {}
}
typescript
自定义 @Public() 装饰器
当控制器全局应用了 JWT 守卫,但某些路由需要免鉴权时,可以通过自定义装饰器 + Reflector 实现:
第一步:创建装饰器
// common/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
typescript
SetMetadata 是 NestJS 提供的装饰器工厂,用于向处理程序附加元数据。
第二步:创建自定义 JwtAuthGuard
// common/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
// 检查路由是否标记为 Public
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(), // 方法级别
context.getClass(), // 控制器级别
]);
if (isPublic) {
return true; // 跳过鉴权
}
// 继承 AuthGuard('jwt') 的默认行为
return super.canActivate(context);
}
}
typescript
Reflector.getAllAndOverride 会同时检查方法和类上的元数据,任一标记即生效。
第三步:在路由上使用
@Controller('user')
@UseGuards(JwtAuthGuard)
export class UserController {
@Public() // 此路由免鉴权
@Get('public-data')
getPublicData() {
return { message: '无需 Token 即可访问' };
}
@Get('profile') // 需要 Token
getProfile() {
return { message: '需要 Token 才能访问' };
}
}
typescript
全局守卫的两种创建方式
方式一:在 main.ts 中注册(无法使用 DI)
// main.ts
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new JwtAuthGuard());
typescript
缺点:无法注入 Service,因为此处绕过了 NestJS 的 DI 容器。
方式二:在 AppModule 中注册(支持 DI)
// app.module.ts
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AppModule {}
typescript
优点:Guard 内部可以通过构造函数注入任意 Service。
| 方式 | DI 支持 | 适用场景 |
|---|---|---|
app.useGlobalGuards() | 不支持 | 简单守卫,不需要 Service |
APP_GUARD provider | 支持 | 需要注入 Service 的复杂守卫 |
全局守卫的执行顺序
当同时存在全局守卫和控制器级别守卫时:
请求 → 全局守卫 → 控制器级别守卫 → 路由处理程序
text
全局守卫先执行,控制器级别守卫后执行。因此如果全局守卫中需要读取 request.user(由 JWT 守卫设置),需确保 JWT 解析在前面完成。
完整示例
// app.module.ts
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
import { AdminGuard } from './common/guards/admin.guard';
@Module({
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard }, // 全局 JWT 鉴权
{ provide: APP_GUARD, useClass: AdminGuard }, // 全局角色检查
],
})
export class AppModule {}
typescript
小结
| 知识点 | 要点 |
|---|---|
@Public() 装饰器 | 通过 SetMetadata + Reflector 实现路由豁免 |
JwtAuthGuard | 继承 AuthGuard('jwt'),覆写 canActivate 增加判断逻辑 |
Reflector.getAllAndOverride | 同时检查方法和类级别的元数据 |
| 全局守卫(main.ts) | 简单但无法使用 DI |
| 全局守卫(APP_GUARD) | 完整 DI 支持,推荐方式 |
| 执行顺序 | 全局守卫 → 控制器守卫 → 路由处理程序 |
↑